#region File information

/**
 * File: SpringCamera.cs
 * Date: 08-03-2008
 * Auth: Jan-Jaap Severs
 * 
 * Desc: A camera that follows a GameObject around, attached to it using a physics spring.
 */

#endregion

#region Using statements

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Gremlin.Game;

#endregion

namespace Gremlin.Components
{
    /// <summary>
    /// A camera that follows a GameObject around, attached to it using a physics spring.
    /// </summary>
    public class SpringCamera : Camera
    {
        #region Member variables

        // The GameObject that's being followed around.
        GameObject _gameObject;

        // The offset of the camera w.r.t. the chased object.
        Vector3 _positionOffset;

        Vector3 _lookAtOffset;

        Vector3 _desiredPosition;

        Vector3 _velocity;

        #endregion

        #region Physics properties

        /// <summary>
        /// Physics coefficient which controls the influence of the camera's position
        /// over the spring force. The stiffer the spring, the closer it will stay to
        /// the chased object.
        /// </summary>
        public float Stiffness
        {
            get { return stiffness; }
            set { stiffness = value; }
        }
        private float stiffness = 3000.0f;

        /// <summary>
        /// Physics coefficient which approximates internal friction of the spring.
        /// Sufficient damping will prevent the spring from oscillating infinitely.
        /// </summary>
        public float Damping
        {
            get { return damping; }
            set { damping = value; }
        }
        private float damping = 600.0f;

        /// <summary>
        /// Mass of the camera body. Heaver objects require stiffer springs with less
        /// damping to move at the same rate as lighter objects.
        /// </summary>
        public float Mass
        {
            get { return mass; }
            set { mass = value; }
        }
        private float mass = 50.0f;

        #endregion

        public SpringCamera(Microsoft.Xna.Framework.Game game, GameObject gameObject)
            : base(game)
        {
            // Store a ref to the GameObject that's being followed around.
            _gameObject = gameObject;

            _positionOffset = new Vector3(0.0f, 1.0f, 2.0f);
            _desiredPosition = Vector3.Zero;
            _lookAtOffset = new Vector3(0, 0.28f, 0);
            _velocity = Vector3.Zero;
        }

        private void UpdateWorldPositions()
        {
            // Construct a matrix to transform from object space to worldspace
            Matrix transform = Matrix.Identity;
            transform.Forward = _gameObject.Direction;
            transform.Up = _gameObject.Up;
            transform.Right = Vector3.Cross(_gameObject.Up, _gameObject.Direction);

            // Calculate desired camera properties in world space
            _desiredPosition = _gameObject.Position +
                Vector3.TransformNormal(_positionOffset, transform);
            _lookAt = _gameObject.Position +
                Vector3.TransformNormal(_lookAtOffset, transform);
        }

        private void UpdateMatrices()
        {
            if (_gameObject == null)
                throw new ArgumentNullException("chasedObject");

            //_viewMatrix = Matrix.CreateLookAt(_position, _lookAt, _gameObject.Up);
            _viewMatrix = Matrix.CreateLookAt(_position, _lookAt, new Vector3(0, 1, 0));
            _projMatrix = Matrix.CreatePerspectiveFieldOfView(_FOV,
                _aspectRatio, _nearClip, _farClip);
        }

        public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            //base.Update(gameTime);

            UpdateWorldPositions();

            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            // Calculate spring force
            Vector3 stretch = _position - _desiredPosition;
            Vector3 force = -stiffness * stretch - damping * _velocity;

            // Apply acceleration
            Vector3 acceleration = force / mass;
            _velocity += acceleration * elapsed;

            // Apply velocity
            _position += _velocity * elapsed;

            UpdateMatrices();
        }
    }
}
